using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using System.Net.Http;
using System.Globalization;

namespace GarminUploader.Connector
{
    class StravaConnector : BaseConnector
    {
        // The required base URL
        protected override string baseUrl { get { return "https://www.strava.com"; } }

        // Client variables
        private string clientId = "585";
        private string clientSecret = "284298577782fececdeac21fb55fce5cd516bdd2";
        private string redirectUri = "http://google.com";

        // URL strings
        private readonly string LOGIN_URL = "/login";
        private readonly string SESSION_URL = "/session";
        private readonly string AUTHORIZE_URL = "/oauth/authorize";
        private readonly string ACCEPT_APP_URL = "/oauth/accept_application";
        private readonly string TOKEN_URL = "/oauth/token";
        private readonly string UPLOAD_URL = "/api/v3/uploads";
        private readonly string LIST_URL = "/api/v3/athlete/activities";

        // User authorization
        private string authenticityToken;
        private string access_token;

        public override void login(string username, string password) 
        {
            // Initialise the user session
            HtmlNodeCollection hiddenInputs = loginGet();

            // Add the login parameters
            List<KeyValuePair<string, string>> loginParams = new List<KeyValuePair<string, string>>();
            loginParams.Add(new KeyValuePair<string, string>("email", username));
            loginParams.Add(new KeyValuePair<string, string>("password", password));

            // Construct the additional URL params
            foreach (HtmlNode input in hiddenInputs)
            {
                // Get the name and value of the input
                string name = input.GetAttributeValue("name", "");
                string value = input.GetAttributeValue("value", "");

                // Set the authenticity token
                if (name.Equals("authenticity_token"))
                {
                    authenticityToken = value;
                }

                // Attach to the URL params
                loginParams.Add(new KeyValuePair<string, string>(name, value));
            }

            // Perform the actual log in
            loginPost(username, password, loginParams);

            // Authorize the user and get the code
            string code = authorizeGet();

            // Exchange tohe code for a token
            tokenPost(code);

            // Set the internal variables
            loggedIn = true;
	    }

        private HtmlNodeCollection loginGet()
        {
            // Initialise the login params
            List<KeyValuePair<string, string>> loginParams = new List<KeyValuePair<string, string>> ();

            // Get the page
            HttpResponseMessage result = client.GetAsync(LOGIN_URL).Result;

            // Get the HTML response
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(result.Content.ReadAsStringAsync().Result);

            // Return the hidden inputs
            return doc.DocumentNode.SelectNodes("//input[@type='hidden']");
        }

        private void loginPost(string username, string password, List<KeyValuePair<string, string>> loginParams)
        {
            // Construct the content
            FormUrlEncodedContent content = new FormUrlEncodedContent(loginParams);

            // Do the POST
            HttpResponseMessage result = client.PostAsync(SESSION_URL, content).Result;

            // Check the response Uri
            if (result.RequestMessage.RequestUri.ToString().Equals(LOGIN_URL))
            {
                throw new UserLoginException(username, LOGIN_URL);
            }
        }

        private string authorizeGet()
        {
            // Construct the URL
            string url = AUTHORIZE_URL +
                            "?client_id=" + clientId +
                            "&redirect_uri=" + redirectUri +
                            "&response_type=code" +
                            "&scope=write";

            // Get the web request for the login page
            HttpResponseMessage result = client.GetAsync(url).Result;

            // If there was no Location header then perform the authorization for the user
            Uri locationUri = result.Headers.Location;
            if (locationUri == null) 
            {
                return authorizePost();
            }
            else
            {
                // Return the code
                return HttpUtility.ParseQueryString(locationUri.Query).Get("code");
            }
        }

        private string authorizePost()
        {
            // Construct the URL
            string url = ACCEPT_APP_URL + 
                            "?client_id=" + clientId +
                            "&redirect_uri=" + redirectUri +
                            "&response_type=code" +
                            "&scope=write";

            // Content
            FormUrlEncodedContent content = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("authenticity_token", authenticityToken)
            });

            // Post the page
            HttpResponseMessage result = client.PostAsync(url, content).Result;

            Uri locationUri = result.Headers.Location;
            return HttpUtility.ParseQueryString(locationUri.Query).Get("code");
        }

        private void tokenPost(string code)
        {
            // Construct the URL
            string url = TOKEN_URL +
                            "?client_id=" + clientId +
                            "&client_secret=" + clientSecret +
                            "&code=" + code;

            // Post the page
            HttpResponseMessage result = client.PostAsync(url, null).Result;
            string responseString = result.Content.ReadAsStringAsync().Result;

            // Deserialise the JSON response
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            var json = serializer.Deserialize<dynamic>(responseString);

            // Set the authorization token
            access_token = json["access_token"];
        }

        public override List<DateTime> listActivityStartTimes()
        {
            // Throw error if user isn't logged in
            checkLogin();

            // Construct the URL
            string url = LIST_URL + "?access_token=" + access_token + "&per_page=200";

            // Return type
            List<DateTime> activityStartTimes = new List<DateTime>();

            // Get the activities
            HttpResponseMessage result = client.GetAsync(url).Result;

            // Deserialise the JSON response
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            var json = serializer.Deserialize<dynamic>(result.Content.ReadAsStringAsync().Result);

            // Add the ID to the array for each activity
            foreach (var activity in json)
            {
                string time = activity["start_date"];
                activityStartTimes.Add(DateTime.Parse(time).ToUniversalTime());
           } 

            // return all external ids
            return activityStartTimes;
        }

        public override void upload(FitFile file)
        {
            // Throw error if user isn't logged in
            checkLogin();

            // Add the post data
            MultipartFormDataContent content = new MultipartFormDataContent();
            content.Add(new StringContent("run"), "\"activity_type\"");
            content.Add(new StringContent(access_token), "\"access_token\"");
            content.Add(new StringContent("fit"), "\"data_type\"");
            content.Add(new ByteArrayContent(file.getBytes()), "\"file\"", file.getName());

            // Post the page
            HttpResponseMessage result = client.PostAsync(UPLOAD_URL, content).Result;
            string responseString = result.Content.ReadAsStringAsync().Result;

            // Deserialise the JSON response
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            var json = serializer.Deserialize<dynamic>(responseString);

            // Throw an exception if there were any failures
            string error = json["error"];
            if (error != null && !error.Equals(""))
            {
                // Throw a special exception if the file is a duplicate
                if (error.Contains("duplicate"))
                {
                    throw new DuplicateActivityException(error);
                }
                else
                {
                    throw new ActivityUploadException("Upload failed: " + error);
                }
            }
        }
    }
}